/* Copyright (c) 2010, mitsuhisa.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *     * Neither the name of the misuhisa.net nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef CONTEXT_HPP_
#define CONTEXT_HPP_

#include <vector>
#include <cassert>
#include "Value.hpp"
#include "Object.hpp"
#include "StrPool.hpp"
#include "Symtbl.hpp"
#include "Handle.hpp"

#ifndef NYU_MAX_ARGS
#define NYU_MAX_ARGS 64
#endif

#ifndef NYU_SCOPE_CACHE_SIZE
#define NYU_SCOPE_CACHE_SIZE 128
#endif

namespace nyu {

class Scope {
public:
    Scope() :
        argc_(0) {

    }

    ~Scope() {
        clear();
    }

    Scope* parent() {
        return parent_;
    }

    void parent(Scope* parent) {
        parent_ = parent;
    }

    Value block() {
        return block_;
    }

    void block(Value block) {
        block_ = block;
    }

    int argc() {
        return argc_;
    }

    void argc(int i) {
        assert(0 <= i && i < NYU_MAX_ARGS);

        argc_ = i;
    }

    Value* argv() {
        return argv_;
    }

    Value argv(int i) {
        if (i < 0 || argc_ <= i)
            return NULL_VALUE;
        return argv_[i];
    }

    void argv(int i, Value v) {
        assert(0 <= i && i < NYU_MAX_ARGS);

        argv_[i] = v;
    }

    void clear() {
        for (int i = 0, j = argc_; i < j; i++) {
            if (is_object(argv_[i]) && !is_null(argv_[i]))
                value_to_object(argv_[i])->unref();
        }
        argc_ = 0;
    }

private:
    Scope* parent_;
    Value block_;
    int argc_;
    Value argv_[NYU_MAX_ARGS];

    std::vector<unsigned int> scope_symbols_;

    friend class Context;
};

struct Symref {
    Value value;
    Scope* scope;
    Symref* next;

    Symref(Value v, Scope* s, Symref* n) :
        value(v), scope(s), next(n) {
    }
};

class Context {
public:
    Context();
    ~Context();

    Value evalFile(const char* filename);
    Value evalString(const char* script);

    void putFunction(const char* name, func_type func);

    Scope* newScope() {
        Scope* scope;

        if (scope_cache_idx_) {
            scope = scope_cache_[--scope_cache_idx_];
        } else {
            scope = new Scope();
        }

        return scope;
    }

    void pushScope(Scope* scope) {
        assert(scope);

        scope->parent(current_scope_);
        current_scope_ = scope;
    }

    void popScope() {
        Scope* scope = current_scope_;
        current_scope_ = scope->parent();

        typedef std::vector<unsigned int>::reverse_iterator iter;
        for (iter it = scope->scope_symbols_.rbegin(), end =
                scope->scope_symbols_.rend(); it != end; ++it) {
            Symref*& ref = symbols_[*it];
            Symref* tmp = ref;
            if (is_object(ref->value) && !is_null(ref->value))
                value_to_object(ref->value)->unref();
            ref = ref->next;
            delete tmp;
        }

        scope->scope_symbols_.clear();

        if (scope_cache_idx_ < NYU_SCOPE_CACHE_SIZE) {
            scope->clear();
            scope_cache_[scope_cache_idx_++] = scope;
        } else {
            delete scope;
        }
    }

    Scope* currentScope() {
        return current_scope_;
    }

    int currentArgc() {
        return current_scope_->argc();
    }

    void currentArgc(int i) {
        current_scope_->argc(i);
    }

    Value* currentArgv() {
        return current_scope_->argv();
    }

    Value currentArgv(int i) {
        return current_scope_->argv(i);
    }

    void currentArgv(int i, Value v) {
        current_scope_->argv(i, v);
    }

    Value currentBlock() {
        return current_scope_->block();
    }

    bool isUpdated() const {
        return is_updated_;
    }

    void setIsUpdated(bool is_updated) {
        is_updated_ = is_updated;
    }

    Value getSymbol(const char* name) {
        Symref* ref = getSymref(Symtbl::getIndex(StrPool::get(name, true)));
        if (!ref)
            return NULL_VALUE;
        return ref->value;
    }

    Symref* getSymref(unsigned int idx) {
        if (symbols_.size() <= idx)
            updateSymbols();
        return symbols_[idx];
    }

    Symref* putSymref(unsigned int idx, Value value) {
        if (symbols_.size() <= idx)
            updateSymbols();

        Symref*& ref = symbols_[idx];

        // TODO cache it
        ref = new Symref(value, current_scope_, ref);

        current_scope_->scope_symbols_.push_back(idx);

        return ref;
    }

    Symref* putSymref(unsigned int idx, Object* value) {
        if (value)
            value->ref();
        return putSymref(idx, object_to_value(value));
    }

    Value currentTarget() {
        return current_target_;
    }

    void currentTarget(Value target) {
        if (is_object(current_target_) && !is_null(current_target_))
            value_to_object(current_target_)->unref();
        current_target_ = target;
    }

    Value call(Value val) {
        assert(is_object(val) && !is_null(val));

        value_to_object(val)->ref();

        Scope* scope = newScope();
        scope->argc(1);

        {
            Value target = currentTarget();
            if (is_object(target) && !is_null(target))
                value_to_object(target)->ref();
            scope->argv(0, target);
        }

        scope->block(val);

        pushScope(scope);

        call_val:

        assert(is_object(val) && !is_null(val));

        {
            Handle<Object> obj(value_to_object(val));
            val = obj->opCall(*this);
        }

        if (isUpdated()) {
            setIsUpdated(false);
            goto call_val;
        }

        popScope();

        return val;
    }

    Value call(Value val, Value arg1) {
        assert(is_object(val) && !is_null(val));

        value_to_object(val)->ref();

        Scope* scope = newScope();
        scope->argc(2);

        {
            Value target = currentTarget();
            if (is_object(target) && !is_null(target))
                value_to_object(target)->ref();
            scope->argv(0, target);
        }

        scope->argv(1, arg1);

        scope->block(val);

        pushScope(scope);

        call_val:

        assert(is_object(val) && !is_null(val));

        {
            Handle<Object> obj(value_to_object(val));
            val = obj->opCall(*this);
        }

        if (isUpdated()) {
            setIsUpdated(false);
            goto call_val;
        }

        popScope();

        return val;
    }

private:
    void updateSymbols() {
        symbols_.resize(Symtbl::getSize());
    }

    Scope* current_scope_;

    bool is_updated_;

    int scope_cache_idx_;
    Scope* scope_cache_[NYU_SCOPE_CACHE_SIZE];

    std::vector<Symref*> symbols_;

    Value current_target_;
};

}

#endif /* CONTEXT_HPP_ */
